package logic;

import logic.ID3Tree;
import weka.core.Attribute;
import weka.core.Instance;
import weka.core.Instances;

public class MushroomID3 extends MushroomClassifier {
    public ID3Tree model = new ID3Tree();
    
    public MushroomID3() {
        
    }
    
    @Override
    public void buildClassifier(Instances inst) {
        initClassValues(inst.classAttribute());
        Instances toDel = new Instances(inst); 
        
        model = constructTree(inst, toDel);
    }
    
    public ID3Tree constructTree(Instances ori, Instances inst) {
        System.out.println(inst.numAttributes());
        if (inst.numAttributes() == 1) {
            ID3Tree id3 = new ID3Tree();
            double d = 0;
            
            for (int i = 0; i < inst.attribute(0).numValues(); i++, d += 1) {
                double clsVal = allSame(inst, 0, d);
                if (clsVal < 0) {
                    clsVal = 0;
                }
                id3.children.put(d, new ID3Tree(clsVal));
            }
            return id3;
        }
        
        Attribute atr = selectNode(inst);
        int idx = atr.index();
        int realIdx = realNodeNum(ori, atr);
        double d = 0;
        ID3Tree id3 = new ID3Tree();
        
        for (int i = 0; i < atr.numValues(); i++, d += 1) {
            double clsVal = allSame(inst, idx, d);
            if (clsVal >= 0) {
                id3.children.put(d, new ID3Tree(realIdx));
            } else {
                Instances del = new Instances(inst);
                del.deleteAttributeAt(idx);
                id3.children.put(d, constructTree(ori, del));
            }
        }
        return id3;
    }
    
    public double allSame(Instances inst, int atrIdx, double atrValue) {
        double clsValue = -1;
        for (int i = 0; i < inst.numInstances(); i++) {
            Instance in = inst.instance(i);
            if (in.value(atrIdx) != atrValue)
                continue;
            
            if (clsValue == -1) {
                clsValue = in.classValue();
            } else {
                if (clsValue != in.classValue()) {
                    return -1;
                }
            }
        }
        
        return clsValue;
    }
    
    public int realNodeNum(Instances inst, Attribute atr) {
        for (int i = 0; i < inst.numAttributes() - 1; i++) {
            if (inst.attribute(i).name().equals(atr.name())) {
                return i;
            }
        }
        return -1;
    }
    
    public Attribute selectNode(Instances inst) {
        double[] IG = new double[inst.numAttributes() - 1];
        for (int i = 0; i < inst.numAttributes() - 1; i++) {
            IG[i] = IG(inst, inst.attribute(i));
        }
        
        return inst.attribute(maxIdx(IG));
    }
    
    public int maxIdx(double[] P) {
        int maxIdx = 0;
        
        for (int i = 1; i < P.length; i++) {
            if (P[i] > P[maxIdx]) {
                maxIdx = i;
            }
        }
        return maxIdx;
    }
    
    public double entropy(double[] P) {
        double ent = 0;
        
        for (int i = 0; i < P.length; i++) {
            if (P[i] == 0)
                return 0;
            ent += P[i]*Math.log(P[i]);
        }
        
        return (ent*-1)/(Math.log(2));
    }
    
    public double IG(Instances inst, Attribute atr) {
        int[] classNumInst = getClassNumInst(inst);
        int numInst = total(classNumInst);
        double[] prob = new double[classNumInst.length];
        for (int i = 0; i < prob.length; i++) {
            prob[i] = ((double)classNumInst[i])/numInst;
        }
        
        return entropy(prob) - remainder(inst, atr);
    }
    
    public double remainder(Instances inst, Attribute atr) {
        double rem = 0;
        int[][] atrNumInst = getAtrNumInst(inst, atr);
        int numInst = total(getClassNumInst(inst));
        
        for (int i = 0; i < atr.numValues(); i++) {
            int numInst4Atr = total(atrNumInst[i]);
            double[] atrProb = new double[atrNumInst[i].length];
            double ent = 0;
            for (int j = 0; j < atrProb.length; j++) {
                atrProb[j] = ((double)atrNumInst[i][j])/numInst4Atr;
            }
            
            rem += ((double)total(atrNumInst[i]))*entropy(atrProb)/numInst;
        }
        
        return rem;
    }
    
    public int total(int[] classCount) {
        int count = 0;
        
        for (int i = 0; i < classCount.length; i++) {
            count += classCount[i];
        }
        
        return count;
    }
    
    public int[] getClassNumInst(Instances inst) {
        int[] classCount = new int[inst.classAttribute().numValues()];
        
        for (int i = 0; i < classCount.length; i++) {
            classCount[i] = getClassNumInst(inst, (double)i);
        }
        
        return classCount;
    }
    
    public int getClassNumInst(Instances inst, double clsVal) {
        int count = 0;
        
        for (int i = 0; i < inst.numInstances(); i++) {
            if (inst.instance(i).classValue() == clsVal)
                count++;
        }
        
        return count;
    }
    
    public int[][] getAtrNumInst(Instances inst, Attribute atr) {
        int[][] result = new int[atr.numValues()][inst.numClasses()];
        for (int i = 0; i < result.length; i++) {
            for (int j = 0; j < result[i].length; j++) {
                result[i][j] = 0;
            }
        }
        int idx = atr.index();
        for (int i = 0; i < inst.numInstances(); i++) {
            Instance in = inst.instance(i);
            result[(int)in.value(idx)][(int)in.classValue()] += 1;
        }
        
        return result;
    }
    
    public int getAtrNumInst(Instances inst, double clsVal, double atrVal) {
        int count = 0;
        
        for (int i = 0; i < inst.numInstances(); i++) {
            Instance in = inst.instance(i);
            if ((in.classValue() == clsVal) && (in.value(i) == atrVal)) {
                count++;
            }
        }
        
        return count;
    }
    
    
    @Override
    public char classify(Instance in) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public char classify(Instance in, boolean[] selectionAttrs) {
        throw new UnsupportedOperationException("Not supported yet.");
    }
    
    public static void main(String[] args) {
        MushroomID3 id = new MushroomID3();
        Instances inst = Loader.load("D:\\mushroom.arff");
        id.buildClassifier(inst);
    }
}
